<?php

namespace Dreamcat\PropertyAnalysis;

use Dreamcat\PropertyAnalysis\Annotation\ArrayDescAnno;
use Dreamcat\PropertyAnalysis\Pojo\AnalysisResult;
use Dreamcat\PropertyAnalysis\Pojo\BuildinType;
use Dreamcat\PropertyAnalysis\Pojo\OneMayTypeDesc;
use Dreamcat\PropertyAnalysis\Pojo\PropertyResult;
use Dreamcat\PropertyAnalysis\Pojo\TypeDescription;
use Dreamcat\PropertyAnalysis\Utils\BuildinTypeHelper;
use JetBrains\PhpStorm\Pure;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionProperty;
use ReflectionType;
use ReflectionUnionType;

/**
 * 类的属性分析器
 * @author vijay
 */
class PropertyAnalysis implements PropertyAnalysisInterface
{
    /** @var int 属性过滤器 */
    private const PROPERTY_FILTER = ReflectionProperty::IS_PUBLIC
    | ReflectionProperty::IS_PROTECTED
    | ReflectionProperty::IS_PRIVATE;
    /** @var AnalysisResult[] 分析结果缓存 */
    private array $result = [];
    /** @var bool 是否分析 setter */
    private bool $analysisSetter = true;

    /**
     * @param ReflectionClass $reflectionClass 要分析的类的反射
     * @return AnalysisResult 分析结果
     */
    public function analysis(ReflectionClass $reflectionClass): AnalysisResult
    {
        if (isset($this->result[$reflectionClass->name])) {
            return $this->result[$reflectionClass->name];
        }

        $this->result[$reflectionClass->name] = $this->analysisClass($reflectionClass);
        return $this->result[$reflectionClass->name];
    }

    /**
     * 分析类的属性列表
     * @param ReflectionClass $reflectionClass 类反射
     * @return AnalysisResult 分析结果
     */
    protected function analysisClass(ReflectionClass $reflectionClass): AnalysisResult
    {
        $result = new AnalysisResult($reflectionClass->name);

        $class = $reflectionClass;
        do {
            foreach ($class->getProperties(self::PROPERTY_FILTER) as $property) {
                if (!$result->getPropertyResult($property->name)) {
                    $result->addPropertyResult($property->name, $this->analysisProperty($property, $reflectionClass));
                    $list[$property->name] = 1;
                }
            }
            $class = $class->getParentClass();
        } while ($class);
        return $result;
    }

    /**
     * 分析属性反射
     * @param ReflectionProperty $property 属性反射
     * @param ReflectionClass $orgClass 原始要分析的类反射
     * @return PropertyResult 分析结果
     * @note $orgClass是对象本身对应的类，$class可能是其祖先类
     */
    protected function analysisProperty(ReflectionProperty $property, ReflectionClass $orgClass): PropertyResult
    {
        $result = new PropertyResult($property);

        # 先看 set 方法的情况
        if ($this->analysisSetter) {
            $result = $this->analysisSetter($orgClass, $property, $result);
        }

        # 再看属性自身
        return $this->analysisPropertySelf($property, $result);
    }

    /**
     * 分析setter方法
     * @param ReflectionClass $orgClass 计划分析的类反射
     * @param ReflectionProperty $property 属性反射
     * @param PropertyResult $result 之前的分析结果
     * @return PropertyResult 分析结果
     * @noinspection PhpDocMissingThrowsInspection
     */
    protected function analysisSetter(
        ReflectionClass $orgClass,
        ReflectionProperty $property,
        PropertyResult $result
    ): PropertyResult {
        $setter = $this->getSetter($orgClass, $this->getPropertySetterName($property));
        if (!$setter) {
            return $result;
        }
        $result->setSetter($setter);
        $result->setSetterSameClass($setter->getDeclaringClass()->name == $property->getDeclaringClass()->name);
        $param = $setter->getParameters()[0];
        /** @noinspection PhpUnhandledExceptionInspection */
        $type = $this->getTypeDescByType(
            $property,
            $param->getType(),
            $param->isOptional() ? $param->getDefaultValue() : null
        );
        if ($type) {
            $result->setSetterTypeDescription($type);
        }

        return $result;
    }

    /** @noinspection PhpDocMissingThrowsInspection */

    /**
     * 获取合法的setter方法反射
     * @param ReflectionClass $orgClass 计划分析的类反射
     * @param string $setterName 方法名称
     * @return ?ReflectionMethod 反射，不存在合法的则返回 null
     */
    protected function getSetter(ReflectionClass $orgClass, string $setterName): ?ReflectionMethod
    {
        if ($orgClass->hasMethod($setterName)) {
            /** @noinspection PhpUnhandledExceptionInspection */
            $setter = $orgClass->getMethod($setterName);
        } else {
            return null;
        }
        if (!$setter->isPublic()) {
            return null;
        }
        if ($setter->isStatic()) {
            return null;
        }
        if (!$setter->getNumberOfParameters()) {
            return null;
        }
        if ($setter->getNumberOfRequiredParameters() > 1) {
            return null;
        }
        return $setter;
    }

    /**
     * 获取属性的 setter 方法名
     * @param ReflectionProperty $reflectionProperty
     * @return string
     */
    protected function getPropertySetterName(ReflectionProperty $reflectionProperty): string
    {
        return "set{$reflectionProperty->name}";
    }

    /**
     * 根据类型声明和默认值生成类型描述
     * @param ReflectionProperty $property 属性反射
     * @param ?ReflectionType $reflectionType 类型声明
     * @param mixed $defaultValue 默认值
     * @return TypeDescription|null
     */
    protected function getTypeDescByType(
        ReflectionProperty $property,
        ?ReflectionType $reflectionType,
        mixed $defaultValue
    ): ?TypeDescription {
        $typeResult = new TypeDescription(
            $reflectionType,
            $property->getDefaultValue(),
            !$reflectionType || $reflectionType->allowsNull()
        );
        if ($reflectionType) {
            $list = $reflectionType instanceof ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType];
        } else {
            $list = [];
        }
        /** @var ReflectionNamedType $namedType */
        foreach ($list as $namedType) {
            $one = $this->analysisType($namedType, $property);
            if ($one) {
                $typeResult->addTypeDescription($one);
            }
        }
        if (!is_null($defaultValue)) {
            /**
             * @var string $type
             * @var callable $fn
             */
            foreach (BuildinTypeHelper::CHECK_FUNC as $type => $fn) {
                if (!$fn($defaultValue)) {
                    continue;
                }
                if ($typeResult->getTypeDescription($type)) {
                    break;
                }
                $typeResult->addTypeDescription(new OneMayTypeDesc($type));
            }
        }
        return $typeResult->getTypeDescriptions() ? $typeResult : null;
    }

    /** @noinspection PhpDocMissingThrowsInspection */

    /**
     * 针对属性的类型声明解析其值要求
     * @param ReflectionNamedType $namedType 一个类型声明
     * @param ReflectionProperty $property 属性反射
     * @return ?OneMayTypeDesc 分析结果，无结果返回null
     */
    private function analysisType(ReflectionNamedType $namedType, ReflectionProperty $property): ?OneMayTypeDesc
    {
        if (class_exists($namedType->getName()) || interface_exists($namedType->getName())) {
            /** @noinspection PhpUnhandledExceptionInspection */
            $one = new OneMayTypeDesc(new ReflectionClass($namedType->getName()));
        } elseif (isset(BuildinTypeHelper::IS_SCALAR[$namedType->getName()])) {
            $one = new OneMayTypeDesc($namedType->getName());
            if ($namedType->getName() != BuildinType::ARRAY) {
                return $one;
            }
            $arrayDesc = $property->getAttributes(ArrayDescAnno::class);
            if (!$arrayDesc) {
                return $one;
            }
            /** @var ArrayDescAnno $arrayDesc */
            $arrayDesc = $arrayDesc[0]->newInstance();
            $one->setIsMap($arrayDesc->isMap());
            $element = new TypeDescription();
            if ($arrayDesc->isElementIsClass()) {
                if (class_exists($arrayDesc->getElementType())) {
                    /** @noinspection PhpUnhandledExceptionInspection */
                    $type = new OneMayTypeDesc(new ReflectionClass($arrayDesc->getElementType()));
                    $element->addTypeDescription($type);
                }
            } else {
                $element->addTypeDescription(new OneMayTypeDesc($arrayDesc->getElementType()));
            }
            if ($element->getTypeDescriptions()) {
                $one->setArrayElement($element);
            }
        } else {
            # 忽略无法识别的类型
            return null;
        }
        return $one;
    }

    /**
     * 分析属性自身
     * @param ReflectionProperty $property 属性反射
     * @param PropertyResult $result 之前的分析结果
     * @return PropertyResult 分析结果
     */
    protected function analysisPropertySelf(
        ReflectionProperty $property,
        PropertyResult $result
    ): PropertyResult {
        $typeResult = $this->getTypeDescByType($property, $property->getType(), $property->getDefaultValue());
        if ($typeResult) {
            $result->setTypeDescription($typeResult);
        }
        return $result;
    }

    /** @noinspection PhpDocMissingThrowsInspection */

    /**
     * @return bool
     */
    #[Pure]
    public function isAnalysisSetter(): bool
    {
        return $this->analysisSetter;
    }

    /**
     * @param bool $analysisSetter 是否分析 setter
     */
    public function setAnalysisSetter(bool $analysisSetter): void
    {
        if ($analysisSetter != $this->analysisSetter) {
            # 变更时修改缓存
            $this->result = [];
        }
        $this->analysisSetter = $analysisSetter;
    }
}

# end of file
